package com.sandwormz.webcompiler;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.io.Files;
import com.sandwormz.webcompiler.resources.LocalFileNotFound;
import com.sandwormz.webcompiler.resources.RemoteFileNotFound;
import com.sandwormz.webcompiler.resources.ResourceNotLoaded;

/**
 * A utility class representing a file and its contents.
 * 
 * $HeadURL$
 * 
 * @author mbryant
 */
class ResourceWithContents {

    public static class FileWithContentsBuilder {
        private String path;
        private String content;

        @SuppressWarnings("synthetic-access")
        public ResourceWithContents build() throws ResourceNotLoaded {
            Preconditions.checkState(this.path != null, "path is required.");
            if (null == this.content) {
                return new ResourceWithContents(this.path);
            } else {
                return new ResourceWithContents(this.path, this.content);
            }
        }

        public FileWithContentsBuilder content(final String content) {
            this.content = content;
            return this;
        }

        public FileWithContentsBuilder path(final String path) {
            this.path = path;
            return this;
        }
    }

    public static FileWithContentsBuilder Builder() {
        return new FileWithContentsBuilder();
    }

    private String path;
    private String parentPath;
    private String content;
    private String filename;

    private ResourceWithContents(final String path) throws ResourceNotLoaded {
        Preconditions.checkArgument(path != null, "The path may not be null.");
        setPath(path);
        if (isRemoteResource(path)) {
            this.content = loadContentFromUrl(path);
        } else {
            this.content = loadContentFromFile(path);
        }
    }

    private ResourceWithContents(final String path, final String content) {
        setPath(path);
        this.content = content;
    }

    /**
     * @return the content
     */
    public String getContent() {
        return this.content;
    }

    /**
     * Return the name component only of this file.
     * 
     * @return
     */
    public String getFileName() {
        return this.filename;
    }

    /**
     * @return
     */
    public String getParentPath() {
        return this.parentPath;
    }

    /**
     * @return the absolutePath
     */
    public String getPath() {
        return this.path;
    }

    public boolean isRemoteResource(final String path) {
        return (path.toLowerCase().startsWith("http://") || (path.toLowerCase()
                .startsWith("https://")));
    }

    /**
     * @param directory
     *            The directory into which we should move this resource.
     */
    public void moveToDirectory(final String directory) {
        Preconditions.checkArgument(directory.endsWith("/"),
                "Can only move to a directory.");
        String newPath = directory + this.getFileName();
        this.setPath(newPath);
    }

    /**
     * @param content
     *            the content to set
     */
    public void setContent(final String content) {
        this.content = content;
    }

    /**
     * @param path
     */
    public void setPath(final String path) {
        if (isRemoteResource(path)) {
            this.path = path;
            // TODO:
            this.parentPath = null;
            this.filename = null;

        } else {

            File file = new File(path);
            this.path = file.getAbsolutePath();
            this.parentPath =
                    file.getAbsoluteFile().getParentFile().getAbsolutePath()
                            + "/";
            this.filename = file.getName();
        }
    }

    @Override
    public String toString() {
        return "[" + this.getPath() + "]";
    }

    public void write() {
        try {
            File outputFile = new File(this.path);
            Files.createParentDirs(outputFile);
            Files.touch(outputFile);
            Files.write(this.content, outputFile, Charsets.UTF_8);
        } catch (IOException e) {
            throw new PageCompilerError(e);
        }
    }

    /**
     * @param path
     * @throws FileNotFoundException
     */
    private String loadContentFromFile(final String path)
            throws LocalFileNotFound {
        File inputFile = new File(path);
        String content;
        FileInputStream stream = null;
        try {
            stream = new FileInputStream(inputFile);

            FileChannel fc = stream.getChannel();
            MappedByteBuffer bb =
                    fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
            Charset.forName("utf-8");
            content = Charset.forName("utf-8").decode(bb).toString();
        } catch (FileNotFoundException e) {
            throw new LocalFileNotFound(e);
        } catch (IOException e) {
            throw new PageCompilerError(e);
        } finally {
            if (null != stream) {
                try {
                    stream.close();
                } catch (IOException e) {
                    throw new PageCompilerError(e);
                }
            }
        }
        return content;
    }

    /**
     * Fetch and decode the contents of a remote url.
     * 
     * @param url
     *            of the remote content to fetch.
     * @return
     * @throws IOException
     */
    private String loadContentFromUrl(final String url)
            throws ResourceNotLoaded {
        try {
            HttpURLConnection con =
                    (HttpURLConnection) new URL(url).openConnection();

            Pattern p = Pattern.compile("text/html;\\s+charset=([^\\s]+)\\s*");
            Matcher m = p.matcher(con.getContentType());
            /*
             * If Content-Type doesn't match this pre-conception, choose default
             * and hope for the best.
             */
            String charset = m.matches() ? m.group(1) : "ISO-8859-1";
            Reader r = new InputStreamReader(con.getInputStream(), charset);
            StringBuilder buf = new StringBuilder();
            while (true) {
                int ch = r.read();
                if (ch < 0) {
                    break;
                }
                buf.append((char) ch);
            }
            return buf.toString();
        } catch (IOException e) {
            throw new RemoteFileNotFound(e);
        }
    }
}